/* Copyright (c) 2004-2024 Peigen.info. All rights reserved. */

package info.peigen.hotpot.manage.core.config;

import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.context.model.SaResponse;
import cn.dev33.satoken.router.SaHttpMethod;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.solon.integration.SaTokenInterceptor;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.http.ContentType;
import cn.hutool.http.Header;
import com.github.xiaoymin.knife4j.solon.extension.OpenApiExtensionResolver;
import info.peigen.hotpot.manage.core.enums.SysBuildInEnum;
import info.peigen.hotpot.manage.core.handler.GlobalExceptionUtil;
import info.peigen.hotpot.manage.core.listener.CommonDataChangeEventCenter;
import info.peigen.hotpot.manage.core.listener.CommonDataChangeListener;
import org.noear.solon.annotation.Bean;
import org.noear.solon.annotation.Configuration;
import org.noear.solon.annotation.Import;
import org.noear.solon.annotation.Inject;
import org.noear.solon.data.dynamicds.DynamicDataSource;
import org.noear.solon.docs.DocDocket;
import org.noear.solon.docs.models.ApiInfo;
import org.noear.solon.vault.annotation.VaultInject;
import org.noear.solon.web.staticfiles.StaticMappings;
import org.noear.solon.web.staticfiles.repository.ClassPathStaticRepository;

import javax.sql.DataSource;
import java.util.List;

/**
 * <b>(HotpotManageCoreAutoConfiguration)</b>
 *
 * @author Peigen
 * @version 1.0.0
 * @since 2023/6/17
 */
@Configuration
@Import(scanPackages = {"info.peigen.hotpot"})
public class HotpotManageCoreAutoConfiguration {
    /** 无需登录的接口地址集合 */
    private static final String[] NO_LOGIN_PATH_ARR         = {
            /* 主入口 */
            "/",

            /* 静态资源 */
            "/favicon.ico",
            "/doc.html",
            "/webjars/**",
            "/configuration/ui",
            "/configuration/security",
            "/ureport/**",
            "/druid/**",
            "/images/**",

            /* 认证相关 */
            "/auth/getPicCaptcha",
            "/auth/getPhoneValidCode",
            "/auth/login",
            "/auth/getSmokeDetection",

            /* 三方登录相关 */
            "/auth/third/render",
            "/auth/third/callback",

            /* 系统基础配置 */
            "/dev/config/sysBaseList",

            /* 文件下载 */
            "/dev/file/download",

            /* 用户个人中心相关 */
            "/sys/userCenter/getPicCaptcha",
            "/sys/userCenter/findPasswordGetPhoneValidCode",
            "/sys/userCenter/findPasswordGetEmailValidCode",
            "/sys/userCenter/findPasswordByPhone",
            "/sys/userCenter/findPasswordByEmail"
    };
    /** 仅超管使用的接口地址集合 */
    private static final String[] SUPER_PERMISSION_PATH_ARR = {
            "/auth/session/**",
            "/auth/third/page",
            "/client/user/**",
            "/sys/org/**",
            "/sys/position/**",
            "/sys/button/**",
            "/sys/menu/**",
            "/sys/module/**",
            "/sys/role/**",
            "/sys/user/**",
            "/dev/config/**",
            "/dev/dict/**",
            "/dev/email/**",
            "/dev/file/**",
            "/dev/job/**",
            "/dev/log/**",
            "/dev/message/**",
            "/dev/monitor/**",
            "/dev/sms/**",
            "/gen/basic/**",
            "/gen/config/**",
            "/mobile/menu/**",
            "/mobile/module/**",
    };
    @Inject
    HotpotManageProperties   properties;
    @Inject
    OpenApiExtensionResolver openApiExtensionResolver;

    @Bean(name = "manageDs", typed = true)
    public DataSource manageDs(@VaultInject("${hotpot.datasource.dynamic}") DynamicDataSource ds) {
        return ds;
    }

    @Bean("assetsCommonDocApi")
    public DocDocket commonDocApi() {
        return new DocDocket()
                .info(new ApiInfo()
                        .title("通用接口")
                        .description("通用接口")
                        .contact("Peigen", "", "peigen123@gmail.com")
                        .version("1.0.0"))
                .groupName("通用接口")
                .apis("info.peigen.hotpot.manage.core.manage.controller")
                .basicAuth(openApiExtensionResolver.getSetting().getBasic())
                .vendorExtensions(openApiExtensionResolver.buildExtensions());
    }

    /**
     * 静态资源映射
     *
     * @author xuyuxiang
     * @since 2022/7/25 15:16
     **/
    @Bean
    public void addResourceHandlers() {
        StaticMappings.add("/doc.html", new ClassPathStaticRepository("/META-INF/resources/"));
        StaticMappings.add("/webjars/", new ClassPathStaticRepository("/META-INF/resources/webjars/"));
    }

    /**
     * 注册跨域过滤器
     */
    @Bean(index = -100)
    public SaTokenInterceptor saTokenInit() {
        return new SaTokenInterceptor()
                // 指定拦截路由
                .addInclude("/**")
                .addExclude("/swagger-resources", "/swagger/*")

                // 设置鉴权的接口
                .setAuth(r -> {
                    SaRouter.match("/**")
                            .notMatch(CollectionUtil.newArrayList(NO_LOGIN_PATH_ARR))
                            .check(r1 -> StpUtil.checkLogin());

                    SaRouter.match(CollectionUtil.newArrayList(SUPER_PERMISSION_PATH_ARR))
                            .notMatch(CollectionUtil.newArrayList(NO_LOGIN_PATH_ARR))
                            .check(r1 -> StpUtil.checkRole(SysBuildInEnum.BUILD_IN_ROLE_CODE.getValue()));
                })
                // 前置函数：在每次认证函数之前执行
                .setBeforeAuth(obj -> {

                    // ---------- 设置跨域响应头 ----------
                    SaHolder.getResponse()
                            // 是否可以在iframe显示视图： DENY=不可以 | SAMEORIGIN=同域下可以 | ALLOW-FROM uri=指定域名下可以
                            // .setHeader("X-Frame-Options", "SAMEORIGIN")

                            // 是否启用浏览器默认XSS防护： 0=禁用 | 1=启用 | 1; mode=block 启用, 并在检查到XSS攻击时，停止渲染页面
                            .setHeader("X-XSS-Protection", "1; mode=block")
                            // 禁用浏览器内容嗅探
                            .setHeader("X-Content-Type-Options", "nosniff")
                            // 允许指定域访问跨域资源
                            .setHeader("Access-Control-Allow-Origin", "*")
                            // 允许所有请求方式
                            .setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE")
                            // 有效时间
                            .setHeader("Access-Control-Max-Age", "3600")
                            // 允许的header参数
                            .setHeader("Access-Control-Allow-Headers", "*");

                    // 如果是预检请求，则立即返回到前端
                    SaRouter.match(SaHttpMethod.OPTIONS)
                            // OPTIONS预检请求，不做处理
                            .free(r -> {
                            })
                            .back();
                }).setError(e -> {
                    // 由于过滤器中抛出的异常不进入全局异常处理，所以必须提供[异常处理函数]来处理[认证函数]里抛出的异常
                    // 在[异常处理函数]里的返回值，将作为字符串输出到前端，此处统一转为JSON输出前端
                    SaResponse saResponse = SaHolder.getResponse();
                    saResponse.setHeader(Header.CONTENT_TYPE.getValue(), ContentType.JSON + ";charset=" + CharsetUtil.UTF_8);
                    return GlobalExceptionUtil.getCommonResult((Exception) e);
                });
    }

    /**
     * 注册数据变化事件中心 事件发布器
     *
     * @author xuyuxiang
     * @since 2023/3/3 14:27
     **/
    @Bean
    public void registerListenerList(List<CommonDataChangeListener> dataChangeListenerList) {
        CommonDataChangeEventCenter.registerListenerList(dataChangeListenerList);
    }
}
